﻿using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Input;

namespace OmniKits.Windows.Interop
{
    public static partial class User32
    {
        #region Constants

        public static readonly IntPtr HWndBroadcast = new IntPtr(0xffff);
        public static readonly IntPtr HWndMessage = new IntPtr(-3);

        #endregion

        #region Functions

        #region Windows APIs

        public const string DllName = "user32.dll";

        [DllImport(DllName)]
        public static extern IntPtr LoadIcon(this IntPtr hModule, IntPtr id);
        [DllImport(DllName, CharSet = CharSet.Unicode)]
        public static extern IntPtr LoadIcon(this IntPtr hModule, string name);
        [DllImport(DllName)]
        public static extern bool DestroyIcon(this IntPtr hIcon);

        [DllImport(DllName, SetLastError = true)]
        public static extern bool RegisterHotKey(this IntPtr handle, int id, int modifiers, int vk);

        public static bool RegisterHotKey(this IntPtr handle, int id, ModifierKeys modifiers, Key key)
            => RegisterHotKey(handle, id, (int)modifiers, KeyInterop.VirtualKeyFromKey(key));


        [DllImport(DllName, SetLastError = true)]
        public static extern bool UnregisterHotKey(this IntPtr handle, int id);

        [DllImport(DllName)]
        public static extern IntPtr GetForegroundWindow();

        [DllImport(DllName)]
        public static extern bool SetForegroundWindow(this IntPtr handle);

        [DllImport(DllName, SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern int GetWindowTextLength(this IntPtr handle);

        [DllImport(DllName, SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern int GetWindowText(this IntPtr handle, StringBuilder buffer, int size);

        [DllImport(DllName, SetLastError = true)]
        public static extern int GetWindowLong(this IntPtr handle, int index);
        public static int GetWindowLong(this IntPtr handle, WindowLongIndex index)
            => handle.GetWindowLong((int)index);

        [DllImport(DllName, SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr GetWindowLongPtr(this IntPtr handle, int index);
        public static IntPtr GetWindowLongPtr(this IntPtr handle, WindowLongIndex index)
            => handle.GetWindowLongPtr((int)index);

        [DllImport(DllName, SetLastError = true)]
        public static extern int SetWindowLong(this IntPtr handle, int index, int value);
        public static int SetWindowLong(this IntPtr handle, WindowLongIndex index, int value)
            => handle.SetWindowLong((int)index, value);

        [DllImport(DllName, SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr SetWindowLongPtr(this IntPtr handle, int index, IntPtr value);
        public static IntPtr SetWindowLongPtr(this IntPtr handle, WindowLongIndex index, IntPtr value)
            => handle.SetWindowLongPtr((int)index, value);

        [DllImport(DllName, SetLastError = true)]
        public static extern bool SetWindowPos(this IntPtr handle, IntPtr handleInsertAfter, int X, int Y, int cx, int cy, int flags);
        public static bool SetWindowPos(this IntPtr handle, IntPtr handleInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags flags)
            => handle.SetWindowPos(handleInsertAfter, X, Y, cx, cy, (int)flags);


        [DllImport(DllName, SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern int SendMessage(this IntPtr handle, int message, IntPtr wParam, IntPtr lParam);
        public static int SendMessage(this IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
            => handle.SendMessage((int)message, wParam, lParam);

        [DllImport(DllName, SetLastError = true)]
        public static extern bool GetWindowDisplayAffinity(this IntPtr handle, out int affinity);
        public static bool GetWindowDisplayAffinity(this IntPtr handle, out WindowDisplayAffinity affinity)
        {
            int intAffinity;
            var result = handle.GetWindowDisplayAffinity(out intAffinity);
            affinity = (WindowDisplayAffinity)intAffinity;
            return result;
        }

        [DllImport(DllName, SetLastError = true)]
        public static extern bool SetWindowDisplayAffinity(this IntPtr handle, int affinity);
        public static bool SetWindowDisplayAffinity(this IntPtr handle, WindowDisplayAffinity affinity)
            => handle.SetWindowDisplayAffinity((int)affinity);

        #endregion

        #region Helper Functions

        public static int ToWindowPosInt(this double value)
            => (int)((0.0 >= value) ? (value - 0.5) : (value + 0.5));

        public static WindowStylesEx GetWindowStylesEx(this IntPtr handle)
            => (WindowStylesEx)GetWindowLong(handle, WindowLongIndex.ExStyle);

        public static WindowStylesEx SetWindowStylesEx(this IntPtr handle, WindowStylesEx exStyle)
            => (WindowStylesEx)SetWindowLong(handle, WindowLongIndex.ExStyle, (int)exStyle);

        public static IntPtr GetWindowPointer(this IntPtr handle, int index)
        {
            if (IntPtr.Size < 8)
                return (IntPtr)handle.GetWindowLong(index);
            else
                return handle.GetWindowLongPtr(index);
        }
        public static IntPtr GetWindowPointer(this IntPtr handle, WindowLongIndex index)
            => handle.GetWindowPointer((int)index);

        public static IntPtr SetWindowPointer(this IntPtr handle, int index, IntPtr value)
        {
            if (IntPtr.Size < 8)
                return (IntPtr)handle.SetWindowLong(index, (int)value);
            else
                return handle.SetWindowLongPtr(index, value);
        }
        public static IntPtr SetWindowPointer(this IntPtr handle, WindowLongIndex index, IntPtr value)
            => handle.SetWindowPointer((int)index, value);

        public static void UpdateWindowLong(this IntPtr handle, int index, Func<int, int> func)
        {
            int @new, last, current = handle.GetWindowLong(index);
            do
            {
                last = current;
                @new = func(last);
                current = handle.SetWindowLong(index, @new);
            } while (current != last);
        }
        public static void UpdateWindowLong(this IntPtr handle, WindowLongIndex index, Func<int, int> func)
            => handle.UpdateWindowLong((int)index, func);

        public static void UpdateWindowPointer(this IntPtr handle, int index, Func<IntPtr, IntPtr> func)
        {
            IntPtr @new, last, current = handle.GetWindowPointer(index);
            do
            {
                last = current;
                @new = func(last);
                current = handle.SetWindowPointer(index, @new);
            } while (current != last);
        }
        public static void UpdateWindowPointer(this IntPtr handle, WindowLongIndex index, Func<IntPtr, IntPtr> func)
            => handle.UpdateWindowPointer((int)index, func);

        public static void SetHitTestTransparent(this IntPtr handle, bool value)
        {
            if (value)
                handle.UpdateWindowLong(WindowLongIndex.ExStyle, xs => xs | WS_EX_TRANSPARENT);
            else
                handle.UpdateWindowLong(WindowLongIndex.ExStyle, xs => xs & ~WS_EX_TRANSPARENT);
        }

        public static bool IsWindowDisplayAffinityMonitor(this IntPtr handle)
        {
            WindowDisplayAffinity affinity;
            if (!handle.GetWindowDisplayAffinity(out affinity))
                new Win32Exception(Marshal.GetLastWin32Error());
            return (affinity & WindowDisplayAffinity.Monitor) == WindowDisplayAffinity.Monitor;
        }

        #endregion

        #endregion
    }
}
